package com.lizi.idgenerator.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.lizi.idgenerator.dto.ResultModel;
import com.lizi.idgenerator.dto.idcreator.IdCreatorResultModel;
import com.lizi.idgenerator.entity.*;
import com.lizi.idgenerator.mapper.*;
import com.lizi.idgenerator.service.CodeCreatorService;
import com.lizi.idgenerator.utils.CodeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import javax.swing.plaf.basic.BasicListUI;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

/**
 * 编码生成器
 * @return
 * @author kingman
 * @since 2020/12/23
 */
@Service
public class CodeCreatorServiceImpl implements CodeCreatorService {

    private static Logger log = LoggerFactory.getLogger(IdCreatorServiceImpl.class);

    @Autowired
    private IdCodeMapper idCodeMapper;
    @Autowired
    private CodeCreatorMapper codeCreatorMapper;
    @Autowired
    private CodeCreatorRecordMapper codeCreatorRecordMapper;


    /**
     * 创建编码
     * @param codes
     * @author kingman
     */
    @Override
    public void addCodes(List<String> codes) {
        if(CollUtil.isEmpty(codes)){
            log.error("codes不能为空");
            return;
        }
        Date now = new Date();
        int i=0;
        int total = codes.size();

        List<IdCode> codeList = new ArrayList<>();
        for (String code : codes){

            i++;
            System.out.println(String.format("%s/%s",i,total));
            IdCode idCode = new IdCode();
            idCode.setCode(code);
            switch (code.length()){
                case 4:
                    idCode.setCodeType(0);
                    break;

            }
            idCode.setCreateTime(now);

            codeList.add(idCode);
            if(codeList.size()==1000){
//                long start = System.currentTimeMillis();
                idCodeMapper.insertBatch(codeList);
//                long end = System.currentTimeMillis();
//                log.info("批量插入费时："+(end-start));
                codeList.clear();
            }

        }
        if(codeList.size()>0){
            idCodeMapper.insertBatch(codeList);
        }
    }

    /**
     * 通过业务Id查询
     *
     * @param businessId
     * @return
     * @author kingman
     */
    @Override
    public CodeCreator getByBusinessId(String businessId) {

        if(StrUtil.isEmpty(businessId)){
            return null;
        }

        LambdaQueryWrapper<CodeCreator> queryWrapper = new QueryWrapper<CodeCreator>().lambda();
        queryWrapper.eq(CodeCreator::getBusinessId,businessId);
        CodeCreator codeCreator = codeCreatorMapper.selectOne(queryWrapper);
        return codeCreator;

    }

    /**
     * 生成编码
     * <p>事务：SERIALIZABLE以下的等级，可重复读，这个事务等级保证了update的一致性，即更新后的数据是准确的，但在并发时，查询的数据是不保证一定是及时的。
     * 这样有个问题，就是idCreator.getCurrentValue()为旧值，还是事务进入后，查询时的数值，有可能不是最新的。所以如果要resultModel.setCurrentValue，
     * 就要拿到最后更新的值-步长 才能得到更新时的那个值。此事务执行该方法平均1.5秒
     * </p>
     * <p>事务：SERIALIZABLE，序列化,代价最高最可靠的隔离级别,如果用该事务等级，就不会出现重复读的问题，但带来较差的性能。
     * 此事务处理该方法2.5秒以上
     * </p>
     * @param businessId
     * @return
     */
    @Override
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public ResultModel<List<String>> generateCode(String businessId) {
        if(StrUtil.isEmpty(businessId))
        {
            return ResultModel.failResult(-2001,"businessId不能为空");
        }
        CodeCreator codeCreator = getByBusinessId(businessId);
        if(codeCreator==null){
            return ResultModel.failResult(-2001,String.format("未找到业务Id（%s）的生成器",businessId));
        }
        Integer maxId = 0;
        List<IdCode> idCodes = getCodes(codeCreator.getCodeType(),codeCreator.getCurrentCodeId(),codeCreator.getStepValue());
        if(idCodes.size()>0) {
            maxId = idCodes.stream().map(IdCode::getId).max(Comparator.naturalOrder()).get();
        }

        if(idCodes.size()<codeCreator.getStepValue())
        {
            int take = codeCreator.getStepValue()-idCodes.size();
            List<IdCode> tmpCodes = getCodes(codeCreator.getCodeType(),0L,take);
            maxId = tmpCodes.stream().map(IdCode::getId).max(Comparator.naturalOrder()).get();
            idCodes.addAll(tmpCodes);
        }

//        if(CollUtil.isEmpty(idCodes)){
//            return ResultModel.failResult(-2002,String.format("idCodes为空，无法生成Id"));
//        }

        Date now = new Date();
        //更新CodeCreator
        LambdaUpdateWrapper<CodeCreator> updateWrapper = new UpdateWrapper<CodeCreator>().lambda();
        updateWrapper.eq(CodeCreator::getId,codeCreator.getId());
        CodeCreator updateCodeCreator = new CodeCreator();
        updateCodeCreator.setUpdateTime(now);
        updateCodeCreator.setCurrentCodeId(maxId.longValue());
        codeCreatorMapper.update(updateCodeCreator,updateWrapper);

        //resultModel.setCurrentValue(idCreator.getCurrentValue()); //事务等级为：SERIALIZABLE以下等级时，这里的赋值是有问题的。


        CodeCreatorRecord record = new CodeCreatorRecord();
        record.setCodeCreatorId(codeCreator.getId());
        record.setCurrentValue(codeCreator.getCurrentCodeId());
        record.setStepValue(codeCreator.getStepValue());
        record.setCreatedValue(maxId.longValue());
        record.setCreateTime(new Date());
        codeCreatorRecordMapper.insert(record);

        List<String> result = new ArrayList<>(idCodes.size());
        CodeFormat codeFormat = new CodeFormat(codeCreator.getCodeFormats());
        for(IdCode code: idCodes)
        {
            String value = codeFormat.format(new Date(), code.getCode());
            result.add(value);
        }

        return ResultModel.successResult(result);
    }

    private List<IdCode> getCodes(int codeType,Long fromId,int takeSize)
    {
        LambdaQueryWrapper<IdCode> queryWrapper = new QueryWrapper<IdCode>().lambda();
        //and id>${CurrentCodeId}
        queryWrapper.gt(IdCode::getId,fromId);
        //and code_type=${codeType}
        queryWrapper.eq(IdCode::getCodeType,codeType);
        // limit 1000
        queryWrapper.last("limit "+takeSize);
        List<IdCode> idCodes = idCodeMapper.selectList(queryWrapper);
        if(CollUtil.isEmpty(idCodes)){
            idCodes = new ArrayList<>(takeSize);
        }

        return idCodes;
    }
}
